﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using System.ComponentModel;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using ZO.SmartCore.Core;

#endregion

namespace ZO.SmartCore.Net.Sockets
{
    /// <summary>
    /// Base Class For all Network Client
    /// </summary>
    public abstract class BaseClientSocket : DisposableObject
    {

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseClientSocket"/> class.
        /// </summary>
        public BaseClientSocket() {}
        
        /// <summary>
        /// Initializes a new instance of the <see cref="BaseClientSocket"/> class.
        /// </summary>
        /// <param name="encoding">The encoding.</param>
        public BaseClientSocket(Encoding encoding)
        {
            this.Encoding = encoding;
        }
        
        #endregion

        #region Destructor

        #endregion

        #region Fields
        private Address _Address = new Address();
        private SocketState _State = SocketState.Unknown;
        private Encoding _Encoding = Encoding.Default;
        private bool _ThrowException = false;
        private int _ReceiveBufferSize = 8192;
        private int _SendBufferSize = 8192;
        private bool _KeepAlive;
        private bool _NoDelay;
        private bool _OutOfBandInline;
        private int _ReceiveTimeout;
        private int _SendTimeout;
        private bool _ReuseAddress;
        #endregion

        #region Events
        /// <summary>
        /// Occurs when the Socket is ConnectCompleted.
        /// </summary>
        public event EventHandler ConnectCompleted;

        /// <summary>
        /// Occurs when the Socket Address is Resolving.
        /// </summary>
        public event EventHandler Resolving;

        /// <summary>
        /// Occurs when the Socket Address is Resolved.
        /// </summary>
        public event EventHandler Resolved;


        /// <summary>
        /// Occurs when the Socket is Disconnecting.
        /// </summary>
        public event EventHandler Disconnecting;

        /// <summary>
        /// Occurs when the Socket is Disconnected.
        /// </summary>
        public event EventHandler DisconnectCompleted;

        /// <summary>
        /// Occurs on any error in Socket.
        /// </summary>
        public event EventHandler<ExceptionEventArgs> Error;


        /// <summary>
        /// Occurs when the Socket is Connecting.
        /// </summary>
        public event EventHandler Connecting;

        #endregion
        #region Delegates

        private delegate void ConnectDisConnectAsyncDelegate();
        #endregion

        #region Operators

        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the encoding to convert string data.
        /// </summary>
        /// <value>The encoding.</value>
        public Encoding Encoding
        {
            get { return _Encoding; }
            set
            {
                if (value != null)
                {
                    _Encoding = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the host (IP) address.
        /// </summary>
        /// <remarks>For connect sockets, the remote address. For Accept sockets, the local address. </remarks>
        public Address Address
        {
            get
            {
                return this._Address;
            }
            protected internal set
            {
                this._Address = value;
            }
        }

        /// <summary>
        /// Gets the state of the socket.
        /// </summary>
        /// <value>The state.</value>
        public SocketState State
        {
            get
            {
                return _State;
            }
            protected internal set
            {
                this._State = value;
            }
        }

        /// <summary>
        /// Returns a reference to the <see cref="System.Net.Sockets.Socket"/> instance being used. 
        /// </summary>
        /// <remarks>
        /// The Socket used for the TCP connection is exposed through this property.
        /// It is not recommended to read or write to the TCP stream using the Socket object directly. 
        /// </remarks>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced)]
        public abstract Socket Socket { get; protected internal set; }

        /// <summary>
        /// Returns a Socket Stream that can be used for streaming operations on the socket. 
        /// </summary>
        /// <exception cref="System.NotImplementedException">
        /// Not Implemented Yet
        /// </exception>
        public abstract SocketStream Stream { get; }

        /// <summary>
        /// Gets a value indicating whether this socket is connected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this socket is connected; otherwise, <c>false</c>.
        /// </value>
        public virtual bool Connected
        {
            get
            {
                if (this.Socket != null)
                {
                    //return true;
                    if (!this.Socket.Connected)
                    {
                        return false;
                    }
                    try
                    {
                        return !this.Socket.Poll(1, SelectMode.SelectError);
                    }
                    catch
                    {
                        return false;
                    }
                }
                return false;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether socket throw exception on error.
        /// </summary>
        /// <value>
        /// 	<see langword="true"/> if throw exception; otherwise, <see langword="false"/>.
        /// </value>
        /// <remarks> if false client can only recieve error using <see cref="Error"/> Event; default value is true</remarks>
        public bool ThrowException
        {
            get { return _ThrowException; }
            set { _ThrowException = value; }
        }


        /// <summary>
        /// Gets and sets the size of the system receive buffer. 
        /// </summary>
        [Category("Socket Options"), Description("Gets and Sets the size of the system receive buffer."), DefaultValue(0x2000)]
        public int ReceiveBufferSize
        {
            get
            {
                if (this.Socket != null)
                {
                    return (int)this.Socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer);
                }
                return this._ReceiveBufferSize;
            }
            set
            {
                this._ReceiveBufferSize = value;
                if ((this.Socket != null) && (this._ReceiveBufferSize != 0))
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, this._ReceiveBufferSize);
                }
            }
        }

        /// <summary>
        /// Gets the amount of data that has been received from the network and is available to be read. 
        /// </summary>
        /// <value>The number of bytes of data that has been received from the network and are available to be read.</value>
        [Description("Returns the Available property of the Socket instance."), Browsable(false)]
        public int Available
        {
            get
            {
                return (this.Connected ? this.Socket.Available : 0);
            }
        }

        /// <summary>
        /// Gets or sets the value of the socket's KeepAlive option. 
        /// </summary>
        [Category("Socket Options"), Description("Gets or Sets the value of the socket's KeepAlive option.")]
        [DefaultValue(false)]
        public bool KeepAlive
        {
            get
            {
                if (this.Socket != null)
                {
                    return Convert.ToBoolean(this.Socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive), 
                        CultureInfo.InvariantCulture);
                }
                return this._KeepAlive;
            }
            set
            {
                this._KeepAlive = value;
                if (this.Socket != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 
                        Convert.ToByte(this._KeepAlive, CultureInfo.InvariantCulture));
                }
            }
        }

        /// <summary>
        /// Gets and sets a value the disables the use of Nagle's algorithm so that data is sent immediately. 
        /// </summary>
        [Category("Socket Options")] 
        [Description("Gets and Sets a value the disables the use of Nagle's algorithm so that data is sent immediately."), DefaultValue(false)]
        public bool NoDelay
        {
            get
            {
                if (this.Socket != null)
                {
                    return Convert.ToBoolean(this.Socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay),
                        CultureInfo.InvariantCulture);
                }
                return this._NoDelay;
            }
            set
            {
                this._NoDelay = value;
                if (this.Socket != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 
                        Convert.ToByte(this._NoDelay, CultureInfo.InvariantCulture));
                }
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether out-of-band (urgent) data is read as normal in-line data. 
        /// </summary>
        [Description("Gets or Sets a value that indicates whether out-of-band (urgent) data will be read as normal in-line data."), Category("Socket Options"), DefaultValue(false)]
        public bool OutOfBandInline
        {
            get
            {
                if (this.Socket != null)
                {
                    return Convert.ToBoolean(this.Socket.GetSocketOption(SocketOptionLevel.Socket, 
                        SocketOptionName.OutOfBandInline), CultureInfo.InvariantCulture);
                }
                return this._OutOfBandInline;
            }
            set
            {
                this._OutOfBandInline = value;
                if (this.Socket != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.OutOfBandInline,
                        Convert.ToByte(this._OutOfBandInline, CultureInfo.InvariantCulture));
                }
            }
        }

        /// <summary>
        /// Gets or sets a value that specifies the amount of time after which a
        /// synchronous Receive call will time out. 
        /// </summary>
        [Description("Gets and Sets the number of milli-seconds that a socket Receive() method will block before throwing a SocketException."), Category("Socket Options"), DefaultValue(0)]
        public int ReceiveTimeout
        {
            get
            {
                if (this.Socket != null)
                {
                    return (int)this.Socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout);
                }
                return this._ReceiveTimeout;
            }
            set
            {
                this._ReceiveTimeout = value;
                if (this.Socket != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, this._ReceiveTimeout);
                }
            }
        }

        /// <summary>
        /// Gets and sets the size of the system send buffer. 
        /// </summary>
        [Description("Gets and Sets the size of the system send buffer."), Category("Socket Options"), DefaultValue(0x2000)]
        public int SendBufferSize
        {
            get
            {
                if (this.Socket != null)
                {
                    return (int)this.Socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer);
                }
                return this._SendBufferSize;
            }
            set
            {
                this._SendBufferSize = value;
                if ((this.Socket != null) && (this._SendBufferSize != 0))
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, this._SendBufferSize);
                }
            }
        }

        /// <summary>
        /// Gets and sets the number of milliseconds that a Send method will 
        /// block before throwing a SocketException. 
        /// </summary>
        [Description("Gets and Sets the number of milli-seconds that a socket Send() method will block before throwing a SocketException."), Category("Socket Options"), DefaultValue(0)]
        public int SendTimeout
        {
            get
            {
                if (this.Socket != null)
                {
                    return (int)this.Socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout);
                }
                return this._SendTimeout;
            }
            set
            {
                this._SendTimeout = value;
                if (this.Socket != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, this._SendTimeout);
                }
            }
        }

        /// <summary>
        /// Gets or sets a Boolean value that specifies whether the Socket allows only one 
        /// process to bind to a port. 
        /// </summary>
        [DefaultValue(false), Category("Socket Options"), Description("Gets or Sets a value that typically allows any local port to be specified, even if it is already in use.")]
        public bool ReuseAddress
        {
            get
            {
                if (this.Socket != null)
                {
                    return Convert.ToBoolean(this.Socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress), CultureInfo.InvariantCulture);
                }
                return this._ReuseAddress;
            }
            set
            {
                this._ReuseAddress = value;
                if (this.Socket != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, Convert.ToInt32(this._ReuseAddress));
                }
            }
        }

        /// <summary>
        /// Returns the local address the socket is bound to. 
        /// </summary>
        [Description("Returns the remote address/port the socket is connected to."), Browsable(false)]
        public IPEndPoint RemoteEndPoint
        {
            get
            {
                if (this.Connected)
                {
                    return this.Address.Endpoint;
                }
                return new IPEndPoint(0, 0);
            }
        }

        /// <summary>
        /// Local IP EndPoint when the socket is connected
        /// </summary>
        [Browsable(false), Description("Local IP EndPoint when the socket is connected")]
        public IPEndPoint LocalEndPoint
        {
            get
            {
                if (this.Connected)
                {
                    return (IPEndPoint)Socket.LocalEndPoint;
                }
                return new IPEndPoint(0, 0);
            }
        }
 

        #endregion

        #region Methods

        /// <summary>
        /// Raises the <see cref="ConnectCompleted"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnConnectCompleted(EventArgs e)
        {
            this.State = SocketState.Connected;

            if (ConnectCompleted != null)
            {
                ConnectCompleted(this, e);
            }
        } // OnConnect

        /// <summary>
        /// Raises the <see cref="Resolving"/> event. 
        /// </summary>
        protected virtual void OnResolving(EventArgs e)
        {
            this.State = SocketState.Resolving;

            if (Resolving != null)
            {
                Resolving(this, e);
            }
        } // OnConnect

        /// <summary>
        /// Raises the <see cref="Resolved"/> event. 
        /// </summary>
        protected virtual void OnResolved(EventArgs e)
        {

            if (Resolved != null)
            {
                Resolved(this, e);
            }
        } // OnConnect

        /// <summary>
        /// Raises the <see cref="DisconnectCompleted"/> event. 
        /// </summary>
        protected virtual void OnDisconnectCompleted(EventArgs e)
        {
            this.State = SocketState.Closed;

            if (DisconnectCompleted != null)
            {
                DisconnectCompleted(this, e);
            }
        } // OnDisconnectCompleted

        /// <summary>
        /// Raises the <see cref="Disconnecting"/> event.
        /// </summary>
        protected virtual void OnDisconnecting(EventArgs e)
        {
            this.State = SocketState.Connecting;

            if (Disconnecting != null)
            {
                this.Disconnecting(this, e);
            }
        } // OnDisconnecting

        /// <summary>
        /// Raises the <see cref="Connecting"/> event. 
        /// </summary>
        protected virtual void OnConnecting(EventArgs e)
        {
            this.State = SocketState.Connecting;

            if (Connecting != null)
            {
                Connecting(this, e);
            }
        } // OnConnect

        /// <summary>
        /// Raises the <see cref="Error"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:SmartCore.Core.ExceptionEventArgs"/> instance containing the event data.</param>
        protected virtual void OnError(ExceptionEventArgs e)
        {
            this.State = SocketState.Error;
            if (Error != null)
            {
                Error(this, e);
            }

            if (ThrowException)
            {
                throw e.Error;
            }
        } // OnError

        /// <overloads>
        /// Connect to a host. 
        /// </overloads>
        /// <summary>
        /// Connect to a host, specifying the local interface and local port to use.
        /// </summary>
        /// <param name="server">Remote hostname or address.</param>
        /// <param name="port">Remote port, a number between 1 and 65535, inclusive.</param>
        [Description("Connect to a server.")]
        public void Connect(string server, int port)
        {
            this.Address.Host = server;
            this.Address.Port = port;
            this.Connect();
        }

        /// <summary>
        /// Connect to a host, specifying the local interface and local port to use.
        /// </summary>
        public abstract void Connect();


        /// <summary>
        /// Disconnects from the address.
        /// </summary>
        public virtual void Disconnect()
        {
            this.Close(null);
        }



        /// <summary>
        /// Close the connection. 
        /// </summary>
        /// <param name="linger">If null is used, a graceful disconnect will occur 
        /// without blocking execution of your application.</param>
        [Description("Close the connection.")]
        public virtual void Close(LingerOption linger)
        {
            if (this.Socket != null)
            {
                this.OnDisconnecting(EventArgs.Empty);

                if (linger != null)
                {
                    this.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, linger);
                }
                else
                {
                    lock (this)
                    {
                        if (this.Socket != null)
                        {
                            Socket socket = this.Socket;
                            this.Socket = null;
                            socket.Close();
                            Thread.Sleep(0);
                            this.State = SocketState.Closed;

                            this.OnDisconnectCompleted(EventArgs.Empty);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Override This Method To Dispose Unmanaged Resources.
        /// </summary>
        protected override void DisposeManagedResources()
        {
            this.Disconnect();
            base.DisposeManagedResources();
        }

        /// <summary>
        /// Begins an asynchronous request for a remote host connection.
        /// </summary>
        public virtual void ConnectAsync()
        {
            ConnectDisConnectAsyncDelegate asyncConnect = new ConnectDisConnectAsyncDelegate(this.Connect);

            asyncConnect.BeginInvoke(new AsyncCallback(this.EndConnect), null);

        }

        /// <summary>
        /// Begins an asynchronous request for a remote host connection.
        /// </summary>
        /// <param name="server">Remote hostname or address.</param>
        /// <param name="port">Remote port, a number between 1 and 65535, inclusive.</param>
        [Description("Connect to a server.")]
        public void ConnectAsync(string server, int port)
        {
            this.Address.Host = server;
            this.Address.Port = port;
            this.ConnectAsync();
        }

        private void EndConnect(IAsyncResult asyncResult)
        {
            ConnectDisConnectAsyncDelegate asyncConnect = (ConnectDisConnectAsyncDelegate)((AsyncResult)asyncResult).AsyncDelegate;
            asyncConnect.EndInvoke(asyncResult);
        }

        /// <summary>
        /// Begins an asynchronous request for a remote host dosconnection.
        /// </summary>
        public virtual void DisconnectAsync()
        {
            ConnectDisConnectAsyncDelegate asyncDisconnect = new ConnectDisConnectAsyncDelegate(this.Disconnect);

            asyncDisconnect.BeginInvoke(new AsyncCallback(this.EndDisconnect), null);

        }

        private void EndDisconnect(IAsyncResult asyncResult)
        {
            ConnectDisConnectAsyncDelegate asyncDisconnect = (ConnectDisConnectAsyncDelegate)((AsyncResult)asyncResult).AsyncDelegate;
            asyncDisconnect.EndInvoke(asyncResult);
        }
        #endregion
    }
}
